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 @@
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 @@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 @@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 @@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 @@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
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:
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
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
DateTime:America/New_York
, Ref:Users
\",\"enum\":[\"Any\",\"Text\",\"Numeric\",\"Int\",\"Bool\",\"Date\",\"DateTime:$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. [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/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
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:
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
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
DateTime:America/New_York
, Ref:Users
\",\"enum\":[\"Any\",\"Text\",\"Numeric\",\"Int\",\"Bool\",\"Date\",\"DateTime:$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. [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/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 @@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 @@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
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:
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
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
DateTime:America/New_York
, Ref:Users
\",\"enum\":[\"Any\",\"Text\",\"Numeric\",\"Int\",\"Bool\",\"Date\",\"DateTime:$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. [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/